 /*******************************************************************************
  * Copyright (c) 2004, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal;

 import org.eclipse.core.runtime.IProgressMonitor;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.jobs.Job;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.util.Geometry;
 import org.eclipse.swt.graphics.GC;
 import org.eclipse.swt.graphics.Image;
 import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.ui.IWorkbenchPreferenceConstants;
 import org.eclipse.ui.internal.util.PrefUtil;

 /**
  * This job creates an animated rectangle that moves from a source rectangle to
  * a target in a fixed amount of time. To begin the animation, instantiate this
  * object then call schedule().
  *
  * @since 3.0
  */
 public class RectangleAnimation extends Job {
     private static class AnimationFeedbackFactory {
         /**
          * Determines whether or not the system being used is
          * sufficiently fast to support image animations.
          *
          * Assumes that a pixel is ~3 bytes
          *
          * For now we use a base limitation of 50MB/sec as a
          * 'reverse blt' rate so that a 2MB size shell can be
          * captured in under 1/25th of a sec.
          */
         private static final int IMAGE_ANIMATION_THRESHOLD = 25; // Frame captures / Sec
 private static final int IMAGE_ANIMATION_TEST_LOOP_COUNT = 20; // test the copy 'n' times

         //private static double framesPerSec = 0.0;

         public static double getCaptureSpeed(Shell wb) {
             // OK, capture
 Rectangle bb = wb.getBounds();
             Image backingStore = new Image(wb.getDisplay(), bb);
             GC gc = new GC(wb);
             
             // Loop 'n' times to average the result
 long startTime = System.currentTimeMillis();
             for (int i = 0; i < IMAGE_ANIMATION_TEST_LOOP_COUNT; i++)
                 gc.copyArea(backingStore, bb.x, bb.y);
             gc.dispose();
             long endTime = System.currentTimeMillis();
             
             // get Frames / Sec
 double fps = IMAGE_ANIMATION_TEST_LOOP_COUNT / ((endTime-startTime) / 1000.0);
             double pps = fps * (bb.width*bb.height*4); // 4 bytes/pixel
 System.out.println("FPS: " + fps + " Bytes/sec: " + (long)pps); //$NON-NLS-1$ //$NON-NLS-2$
 return fps;
         }
         
         public boolean useImageAnimations(Shell wb) {
             return getCaptureSpeed(wb) >= IMAGE_ANIMATION_THRESHOLD;
         }
         
         public static DefaultAnimationFeedback createAnimationRenderer(Shell parentShell) {
             // on the first call test the animation threshold to determine
 // whether to use image animations or not...
 // if (framesPerSec == 0.0)
 // framesPerSec = getCaptureSpeed(parentShell);
 //
 // IPreferenceStore preferenceStore = PrefUtil.getAPIPreferenceStore();
 // boolean useNewMinMax = preferenceStore.getBoolean(IWorkbenchPreferenceConstants.ENABLE_NEW_MIN_MAX);
 //
 // if (useNewMinMax && framesPerSec >= IMAGE_ANIMATION_THRESHOLD) {
 // return new ImageAnimationFeedback();
 // }
 //
 return new DefaultAnimationFeedback();
         }
     }
     
     // Constants
 public static final int TICK_TIMER = 1;
     public static final int FRAME_COUNT = 2;

     // Animation Parameters
 private Display display;
     
     private boolean enableAnimations;
     private int timingStyle = TICK_TIMER;
     private int duration;
     
     // Control State
 private DefaultAnimationFeedback feedbackRenderer;
     private long stepCount;
     private long frameCount;
     private long startTime;
     private long curTime;
     private long prevTime;
     
     // Macros
 private boolean done() { return amount() >= 1.0; }

     public static Rectangle interpolate(Rectangle start, Rectangle end,
             double amount) {
         double initialWeight = 1.0 - amount;

         Rectangle result = new Rectangle((int) (start.x * initialWeight + end.x
                 * amount), (int) (start.y * initialWeight + end.y * amount),
                 (int) (start.width * initialWeight + end.width * amount),
                 (int) (start.height * initialWeight + end.height * amount));

         return result;
     }
     
     // Animation Step
 private Runnable animationStep = new Runnable () {

         public void run() {
             // Capture time
 prevTime = curTime;
             curTime = System.currentTimeMillis();

             // Has the system timer 'ticked'?
 if (curTime != prevTime) {
                 clockTick();
             }
             
             if (isUpdateStep()) {
                 updateDisplay();
                 frameCount++;
             }
             
             stepCount++;
         }

     };
     
     /**
      * Creates an animation that will morph the start rectangle to the end rectangle in the
      * given number of milliseconds. The animation will take the given number of milliseconds to
      * complete.
      *
      * Note that this is a Job, so you must invoke schedule() before the animation will begin
      *
      * @param whereToDraw specifies the composite where the animation will be drawn. Note that
      * although the start and end rectangles can accept any value in display coordinates, the
      * actual animation will be clipped to the boundaries of this composite. For this reason,
      * it is good to select a composite that encloses both the start and end rectangles.
      * @param start initial rectangle (display coordinates)
      * @param end final rectangle (display coordinates)
      * @param duration number of milliseconds over which the animation will run
      */
     public RectangleAnimation(Shell parentShell, Rectangle start,
             Rectangle end, int duration) {
         super(WorkbenchMessages.RectangleAnimation_Animating_Rectangle);

         // if animations aren't on this is a NO-OP
 IPreferenceStore preferenceStore = PrefUtil.getAPIPreferenceStore();
         enableAnimations = preferenceStore.getBoolean(IWorkbenchPreferenceConstants.ENABLE_ANIMATIONS);
         
         if (!enableAnimations) {
             return;
         }

         // Capture paraeters
 display = parentShell.getDisplay();
         this.duration = duration;

         // Don't show the job in monitors
 setSystem(true);
         
         // Pick the renderer (could be a preference...)
 feedbackRenderer = AnimationFeedbackFactory.createAnimationRenderer(parentShell);
         
         // Set it up
 feedbackRenderer.initialize(parentShell, start, end);
         
         // Set the animation's initial state
 stepCount = 0;
         //long totalFrames = (long) ((duration / 1000.0) * framesPerSec);
 curTime = startTime = System.currentTimeMillis();
     }

     public RectangleAnimation(Shell parentShell, Rectangle start, Rectangle end) {
         this(parentShell, start, end, 400);
     }
     
     public void addStartRect(Rectangle rect) {
         if (feedbackRenderer != null)
             feedbackRenderer.addStartRect(rect);
     }
     
     public void addEndRect(Rectangle rect) {
         if (feedbackRenderer != null)
             feedbackRenderer.addEndRect(rect);
     }

     public void addStartRect(Control ctrl) {
         Rectangle ctrlBounds = ctrl.getBounds();
         Rectangle startRect = Geometry.toDisplay(ctrl.getParent(), ctrlBounds);
         addStartRect(startRect);
     }

     public void addEndRect(Control ctrl) {
         Rectangle ctrlBounds = ctrl.getBounds();
         Rectangle endRect = Geometry.toDisplay(ctrl.getParent(), ctrlBounds);
         addEndRect(endRect);
     }

     /**
      *
      */
     protected void clockTick() {
     }
     
     /**
      * @return
      */
     protected boolean isUpdateStep() {
         switch (timingStyle) {
             case TICK_TIMER:
                 return prevTime != curTime;
     
             case FRAME_COUNT:
                 return true;
         }
         
         return false;
     }

     private double amount() {
         double amount = 0.0;
         
         switch (timingStyle) {
             case TICK_TIMER:
                 amount = (double) (curTime - startTime) / (double) duration;
                 break;
     
             case FRAME_COUNT:
                 amount = (double)frameCount / (double)duration;
         }
         
         if (amount > 1.0)
             amount = 1.0;
         
         return amount;
     }

     /**
      *
      */
     protected void updateDisplay() {
         feedbackRenderer.renderStep(amount());
     }

     /* (non-Javadoc)
      * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor)
      */
     protected IStatus run(IProgressMonitor monitor) {
         
         // We use preference value to indicate that the animation should be skipped on this platform.
 if (!enableAnimations || feedbackRenderer == null) {
             return Status.OK_STATUS;
         }

         // Do we have anything to animate ?
 boolean isEmpty = feedbackRenderer.getStartRects().size() == 0;
         if (isEmpty) {
             return Status.OK_STATUS;
         }
         
         // We're starting, initialize
 display.syncExec(new Runnable () {
             public void run() {
                 feedbackRenderer.jobInit();
             }
         });
         
         // Only start the animation timer -after- we've initialized
 curTime = startTime = System.currentTimeMillis();
         
         while (!done()) {
             display.syncExec(animationStep);
             // Don't pin the CPU
 Thread.yield();
         }

         //System.out.println("Done: " + (curTime-startTime) + " steps: " + stepCount + " frames:" + frameCount); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
 // We're done, clean up
 display.syncExec(new Runnable () {
             public void run() {
                 feedbackRenderer.dispose();
             }
         });
     
         return Status.OK_STATUS;
     }
 }

